/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.foment.joliage3.sql;

import java.io.PrintWriter;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import javax.sql.DataSource;

/**
 *
 * @author dusan
 */

class PoolConnection implements Connection
{
    private Connection m_Conn;
    private PoolDataSource m_Ds; // Datasource odgovoran za ovu konekciju
    private boolean m_IsClosed = true;
    private Date m_CreationDate;


    private PoolConnection() {}
    //===========================================================================================//
    PoolConnection( Connection conn, PoolDataSource ds )
    {
        m_Conn = conn;
        m_Ds = ds;
        m_IsClosed = false;
        m_CreationDate = new Date( System.currentTimeMillis() );
    }
    //===========================================================================================//
    public boolean isClosed() throws SQLException
    {
        return m_IsClosed;
    }
    //===========================================================================================//
    public void close() throws SQLException
    {
        m_IsClosed = true;
        try
        {
            m_Ds.returnConnection( m_Conn );
        }
        catch( Exception e )
        {
            e.printStackTrace();
        }
    }
    //===========================================================================================//
    public Date getCreationDate()
    {
        return m_CreationDate;
    }
    //===========================================================================================//
    public void setCreationDate( Date d )
    {
        m_CreationDate = d;
    }
    //===========================================================================================//
    
    public Statement createStatement()throws SQLException
    {
       return m_Conn.createStatement();
    }

    public Statement createStatement(int i,int j)throws SQLException
    {
           return m_Conn.createStatement(i,j);
    }

    public boolean getAutoCommit()throws SQLException
    {
           return m_Conn.getAutoCommit();
    }

    public String getCatalog()throws SQLException{
           return m_Conn.getCatalog();
    }

    public DatabaseMetaData getMetaData()throws SQLException{
           return m_Conn.getMetaData();
    }

    public int getTransactionIsolation()throws SQLException{
           return m_Conn.getTransactionIsolation();
    }

    public Map getTypeMap()throws SQLException{
           return m_Conn.getTypeMap();
    }

    public SQLWarning getWarnings()throws SQLException{
           return m_Conn.getWarnings();
    }

    public boolean isReadOnly()throws SQLException{
           return m_Conn.isReadOnly();
    }

    public void rollback()throws SQLException{
           m_Conn.rollback();
    }

    public void setAutoCommit(boolean sa)throws SQLException{
           m_Conn.setAutoCommit(sa);
    }

    public void setCatalog(String cat)throws SQLException{
           m_Conn.setCatalog(cat);
    }

    public void setReadOnly(boolean ro)throws SQLException{
           m_Conn.setReadOnly(ro);
    }

    public void setTransactionIsolation(int lev)throws SQLException{
           m_Conn.setTransactionIsolation(lev);
    }

    public PreparedStatement prepareStatement(String s)throws SQLException{
           return m_Conn.prepareStatement(s);
    }

    public CallableStatement prepareCall(String s)throws SQLException{
           return m_Conn.prepareCall(s);
    }

    public String nativeSQL(String s)throws SQLException{
           return m_Conn.nativeSQL(s);
    }

    public PreparedStatement prepareStatement(String s,int i,int j)throws SQLException{
           return m_Conn.prepareStatement(s,i,j);
    }

    public CallableStatement prepareCall(String s,int i,int j)throws SQLException{
           return m_Conn.prepareCall(s,i,j);
    }

    public void setTypeMap(Map m)throws SQLException{
           m_Conn.setTypeMap(m);
    }

    public PreparedStatement prepareStatement(String s,String[] niz)throws SQLException{
        return m_Conn.prepareStatement(s,niz);
    }

    public Savepoint setSavepoint(String s)throws SQLException{
        return m_Conn.setSavepoint(s);
    }

    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)throws SQLException {
        return m_Conn.prepareCall(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    public PreparedStatement prepareStatement(String sql, int[] columnIndexes)throws SQLException{
        return m_Conn.prepareStatement(sql,columnIndexes);
    }

    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException{
        return m_Conn.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)throws SQLException {
        return m_Conn.prepareStatement(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
    }

    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)throws SQLException {
        return m_Conn.prepareStatement(sql,autoGeneratedKeys);
    }

    public Savepoint setSavepoint() throws SQLException{
        return m_Conn.setSavepoint();
    }

    public void setHoldability(int holdability) throws SQLException {
        m_Conn.setHoldability(holdability);
    }

    public void rollback(Savepoint savepoint) throws SQLException{
        m_Conn.rollback(savepoint);
    }

    public void releaseSavepoint(Savepoint savepoint) throws SQLException{
        m_Conn.releaseSavepoint(savepoint);
    }

    public int getHoldability() throws SQLException{
        return m_Conn.getHoldability();
    }

    public void clearWarnings() throws SQLException {
        m_Conn.clearWarnings();
    }

    public void commit() throws SQLException {
        m_Conn.commit();
    }

    public Array createArrayOf(String string, Object[] os) throws SQLException {
        return m_Conn.createArrayOf(string, os);
    }

    public Blob createBlob() throws SQLException {
        return m_Conn.createBlob();
    }

    public Clob createClob() throws SQLException {
        return m_Conn.createClob();
    }

    public NClob createNClob() throws SQLException {
        return m_Conn.createNClob();
    }

    public SQLXML createSQLXML() throws SQLException {
        return m_Conn.createSQLXML();
    }

    public Struct createStruct(String string, Object[] os) throws SQLException {
        return m_Conn.createStruct(string, os);
    }

    public Properties getClientInfo() throws SQLException {
        return m_Conn.getClientInfo();
    }

    public String getClientInfo(String string) throws SQLException {
        return m_Conn.getClientInfo(string);
    }

    public boolean isValid(int i) throws SQLException {
        return m_Conn.isValid(i);
    }

    public boolean isWrapperFor(Class<?> type) throws SQLException {
        return m_Conn.isWrapperFor(type);
    }

    public void setClientInfo(Properties prprts) throws SQLClientInfoException {
        m_Conn.setClientInfo(prprts);
    }

    public void setClientInfo(String string, String string1) throws SQLClientInfoException {
        m_Conn.setClientInfo( string, string1 );
    }

    public <T> T unwrap(Class<T> type) throws SQLException {
        return m_Conn.unwrap(type);
    }

}

//===========================================================================================//
public class PoolDataSource implements DataSource
{

    private static PoolDataSource pool = null;
    private static String driver;
    private static String jdbcURL;
    
    private Vector pooledCons; //  cuvamo nase konekcije
    private static int maxCons = 10; // broj konekcija koje nas pool moze da cuva
    private static int minutes = 5; // timeout period, ako je konekcija neaktivna za ovu vrednost tada je unistavamo
    private static int conCount; // broj konekcija na poolu
    private static int timeout = 1000 * 60; // timeout period u milisekundama

    //===========================================================================================//
    public static PoolDataSource getInstance() throws Exception
    {
        if( pool == null )
        {
            pool = new PoolDataSource();      
            pool.initializeConnections();
        }
        return pool;
    }
    //===========================================================================================//
    private void initializeConnections() throws SQLException
    {
        Connection c = DriverManager.getConnection( jdbcURL );      
        pooledCons.add( new PoolConnection( c , this) );        
    }
    //===========================================================================================//
    private PoolDataSource() throws Exception
    {
        conCount = 0; // prazan pool
        pool.driver = Database.getJDBCDriver();
        pool.jdbcURL = Database.getJDBCURL();
        Class.forName( driver ).newInstance(); // ucitavamo driver za konekciju sa bazom
        pooledCons = new Vector( maxCons );
        timeout = 1000 * 60 * minutes;
    }
    //===========================================================================================//
    //===========================================================================================//
    //===========================================================================================//
    public int getBusyCount()
    { // broj zauzetih konekcija sa poola
       return conCount;
    }

    public int getFreeCount(){ // broj slobodnih (kreiranih) konekcija na poolu
       return pooledCons.size();
    }

    public void returnConnection( Connection c ) throws Exception
    {
        PoolConnection p = new PoolConnection(c, this);
        p.setCreationDate(new Date( System.currentTimeMillis() )); // azuriramo vreme koriscenja
        pooledCons.add(p); // vracamo je u pool        
        conCount--;
        System.out.println("Veza vracena, total: " + conCount );
    }
    //===========================================================================================//
    public synchronized Connection getConnection() throws SQLException
    {
        if( conCount < maxCons || !pooledCons.isEmpty() ) // mozemo kreirati jos konekcija ili imamo slobodnih
        {
            long currenttime=System.currentTimeMillis();
            while( ! pooledCons.isEmpty() )
            {
                PoolConnection conn = ( PoolConnection ) pooledCons.firstElement();
                pooledCons.removeElementAt( 0 );
                long created = conn.getCreationDate().getTime();
                if( currenttime - created <= timeout ) // ako je validna, vracamo je korisniku
                {
                    conCount++;
                    System.out.println("Slobodna veza nadjena, total " + conCount );
                    return conn;
                }
            }
        }
        if( pooledCons.isEmpty() && conCount < maxCons ) // kreiramo novu konekciju
        {
            try
            {
                initializeConnections();
                Connection conn = ( Connection )pooledCons.firstElement();
                pooledCons.removeElementAt( 0 );
                System.out.println("Veza kreirana, total " + conCount );
                conCount++;
                return conn;
            }
            catch( Exception e )
            {
                e.printStackTrace();
                return null;
            }
        }
        // inace, nemamo ni mesta za nove konekcije niti postoje slobodne.
        //throw new SQLException("More connections required from pool by application than specified in pool settings!");
        return null; // korisnik ce da ceka da se neke oslobode
    }
    //===========================================================================================//
    public Connection getConnection(String string, String string1) throws SQLException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    //===========================================================================================//
    public PrintWriter getLogWriter() throws SQLException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    //===========================================================================================//
    public void setLogWriter(PrintWriter writer) throws SQLException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    //===========================================================================================//
    public void setLoginTimeout(int i) throws SQLException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    //===========================================================================================//
    public int getLoginTimeout() throws SQLException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    //===========================================================================================//
    public <T> T unwrap(Class<T> type) throws SQLException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    //===========================================================================================//
    public boolean isWrapperFor(Class<?> type) throws SQLException {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    //===========================================================================================//
}
